package com.test.mathexpr;

// Created by Lawrence PC Dol.  Released into the public domain.
//
// Contributions by Carlos Gómez of Asturias, Spain, in the area of unary operators
// and right-to-left evaluations proved invaluable to implementing these features.
// Thanks Carlos!
//
// Source is licensed for any use, provided this copyright notice is retained.
// No warranty for any purpose whatsoever is implied or expressed.  The author
// is not liable for any losses of any kind, direct or indirect, which result
// from the use of this software.
// http://www.softwaremonkey.org/Code/MathEval
//package <your-package-here>;

import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;

/**
 * Math Evaluator. Provides the ability to evaluate a String math expression,
 * with support for pureFunctions, variables and standard math constants.
 * <p>
 * Supported Operators:
 * 
 * <pre>
 *     Operator  Precedence  Unary Binding  Description
 *     --------- ----------- -------------- ------------------------------------------------
 *     '='       99 / 99     RIGHT_SIDE     Simple assignment (internal, used for the final operation)
 *     '&circ;'       80 / 81     NO_SIDE        Power
 *     '±'       60 / 60     RIGHT_SIDE     Unary negation (internal, substituted for '-')
 *     '*'       40 / 40     NO_SIDE        Multiple (conventional computer notation)
 *     '×'       40 / 40     NO_SIDE        Multiple (because it's a Unicode world out there)
 *     '·'       40 / 40     NO_SIDE        Multiple (because it's a Unicode world out there)
 *     '('       40 / 40     NO_SIDE        Multiply (implicit due to brackets, e.g &quot;(a)(b)&quot;)
 *     '/'       40 / 40     NO_SIDE        Divide (conventional computer notation)
 *     '÷'       40 / 40     NO_SIDE        Divide (because it's a Unicode world out there)
 *     '%'       40 / 40     NO_SIDE        Remainder
 *     '+'       20 / 20     NO_SIDE        Add/unary-positive
 *     '-'       20 / 20     NO_SIDE        Subtract/unary-negative
 * </pre>
 * 
 * <p>
 * Predefined Constants:
 * 
 * <pre>
 *     Name                 Description
 *     -------------------- ----------------------------------------------------------------
 *     E                    The double value that is closer than any other to e, the base of the natural logarithms (2.718281828459045).
 *     Euler                Euler's Constant (0.577215664901533).
 *     LN2                  Log of 2 base e (0.693147180559945).
 *     LN10                 Log of 10 base e (2.302585092994046).
 *     LOG2E                Log of e base 2 (1.442695040888963).
 *     LOG10E               Log of e base 10 (0.434294481903252).
 *     PHI                  The golden ratio (1.618033988749895).
 *     PI                   The double value that is closer than any other to pi, the ratio of the circumference of a circle to its diameter (3.141592653589793).
 * </pre>
 * 
 * <p>
 * Supported Functions (see java.Math for detail and parameters):
 * <ul>
 * <li>abs
 * <li>acos
 * <li>asin
 * <li>atan
 * <li>cbrt
 * <li>ceil
 * <li>cos
 * <li>cosh
 * <li>exp
 * <li>expm1
 * <li>floor
 * <li>log
 * <li>log10
 * <li>log1p
 * <li>max
 * <li>min
 * <li>random
 * <li>round
 * <li>roundHE (maps to Math.rint)
 * <li>signum
 * <li>sin
 * <li>sinh
 * <li>sqrt
 * <li>tan
 * <li>tanh
 * <li>toDegrees
 * <li>toRadians
 * <li>ulp
 * </ul>
 * <p>
 * Threading Design : [x] Single Threaded [ ] Threadsafe [ ] Immutable [ ]
 * Isolated
 * 
 * @author Lawrence Dol
 * @since Build 2008.0426.1016
 */

public class MathEval extends Object {

	// *************************************************************************************************
	// INSTANCE PROPERTIES
	// *************************************************************************************************

	private Operator[] operators; // operators in effect for this parser
	private final SortedMap<String, Double> constants; // external constants
	private final SortedMap<String, Double> variables; // external variables
	private final SortedMap<String, FunctionHandler> pureFunctions; // external
																	// pureFunctions
	private final SortedMap<String, FunctionHandler> impureFunctions; // external
																		// pureFunctions
	private boolean relaxed; // allow variables to be undefined
	private String separators; // cache of the operators, used for separators
								// for getVariablesWithin()

	private String expression; // expression being evaluated
	private int offset; // used when returning from a higher precedence
						// sub-expression evaluation
	private boolean isConstant; // last expression evaluated is constant

	// *************************************************************************************************
	// INSTANCE CREATE/DELETE
	// *************************************************************************************************

	/**
	 * Create a math evaluator.
	 */
	public MathEval() {
		super();

		operators = new Operator[256];
		DefaultImpl.registerOperators(this);

		constants = new TreeMap<String, Double>(String.CASE_INSENSITIVE_ORDER);
		variables = new TreeMap<String, Double>(String.CASE_INSENSITIVE_ORDER);
		setConstant("E", Math.E);
		setConstant("Euler", 0.577215664901533D);
		setConstant("LN2", 0.693147180559945D);
		setConstant("LN10", 2.302585092994046D);
		setConstant("LOG2E", 1.442695040888963D);
		setConstant("LOG10E", 0.434294481903252D);
		setConstant("PHI", 1.618033988749895D);
		setConstant("PI", Math.PI);

		pureFunctions = new TreeMap<String, FunctionHandler>(
				String.CASE_INSENSITIVE_ORDER);
		impureFunctions = new TreeMap<String, FunctionHandler>(
				String.CASE_INSENSITIVE_ORDER);
		DefaultImpl.registerFunctions(this);

		relaxed = false;
		separators = null;

		offset = 0;
		isConstant = false;
	}

	/**
	 * Create a math evaluator with the same constants, variables, function
	 * handlers and relaxation setting as the supplied evaluator.
	 */
	public MathEval(MathEval oth) {
		super();

		operators = oth.operators;

		constants = new TreeMap<String, Double>(String.CASE_INSENSITIVE_ORDER);
		constants.putAll(oth.constants);

		variables = new TreeMap<String, Double>(String.CASE_INSENSITIVE_ORDER);
		variables.putAll(oth.variables);

		pureFunctions = new TreeMap<String, FunctionHandler>(
				String.CASE_INSENSITIVE_ORDER);
		impureFunctions = new TreeMap<String, FunctionHandler>(
				String.CASE_INSENSITIVE_ORDER);
		pureFunctions.putAll(oth.pureFunctions);
		impureFunctions.putAll(oth.impureFunctions);

		relaxed = oth.relaxed;
		separators = oth.separators;

		offset = 0;
		isConstant = false;
	}

	// *************************************************************************************************
	// INSTANCE METHODS - ACCESSORS
	// *************************************************************************************************

	/**
	 * Set a named constant (constant names are not case-sensitive). Constants
	 * are like variables but are not cleared by clear(). Variables of the same
	 * name have precedence over constants.
	 */
	public double getConstant(String nam) {
		Double val = constants.get(nam);

		return (val == null ? 0 : val.doubleValue());
	}

	/** Gets an unmodifiable iterable of the constants in this evaluator. */
	public Iterable<Map.Entry<String, Double>> getConstants() {
		return Collections.unmodifiableMap(constants).entrySet();
	}

	/**
	 * Set a named constant (constants names are not case-sensitive). Constants
	 * are like variables but are not cleared by clear(). Variables of the same
	 * name have precedence over constants.
	 */
	public MathEval setConstant(String nam, double val) {
		return setConstant(nam, Double.valueOf(val));
	}

	/**
	 * Set a named constant (constants names are not case-sensitive). Constants
	 * are like variables but are not cleared by clear(). Variables of the same
	 * name have precedence over constants.
	 */
	public MathEval setConstant(String nam, Double val) {
		if (constants.get(nam) != null) {
			throw new IllegalArgumentException("Constants may not be redefined");
		}
		validateName(nam);
		constants.put(nam, val);
		return this;
	}

	/**
	 * Set a custom operator, replacing any existing operator with the same
	 * symbol. Operators cannot be removed, only replaced.
	 */
	public MathEval setOperator(Operator opr) {
		if (opr.symbol >= operators.length) { // extend the array if necessary
			Operator[] noa = new Operator[opr.symbol + (opr.symbol % 255) + 1]; // use
																				// allocation
																				// pages
																				// of
																				// 256
			System.arraycopy(operators, 0, noa, 0, operators.length);
			operators = noa;
		}
		operators[opr.symbol] = opr;
		return this;
	}

	/**
	 * Set a pure function handler for the specific named function, replacing
	 * any existing handler for the given name; if the handler is null the
	 * function handler is removed.
	 * <p>
	 * Pure functions have results which depend purely on their arguments; given
	 * constant arguments they will have a constant result. Impure functions are
	 * rare.
	 */
	public MathEval setFunctionHandler(String nam, FunctionHandler hdl) {
		return setFunctionHandler(nam, hdl, false);
	}

	/**
	 * Set a function handler for the specific named function optionally tagging
	 * the function as impure, replacing any existing handler for the given
	 * name; if the handler is null the function handler is removed.
	 * <p>
	 * Pure functions have results which depend purely on their arguments; given
	 * constant arguments they will have a constant result. Impure functions are
	 * rare.
	 */
	public MathEval setFunctionHandler(String nam, FunctionHandler hdl,
			boolean impure) {
		validateName(nam);
		if (hdl == null) {
			pureFunctions.remove(nam);
			impureFunctions.remove(nam);
		} else if (impure) {
			pureFunctions.remove(nam);
			impureFunctions.put(nam, hdl);
		} else {
			pureFunctions.put(nam, hdl);
			impureFunctions.remove(nam);
		}
		return this;
	}

	/** Set a named variable (variables names are not case-sensitive). */
	public double getVariable(String nam) {
		Double val = variables.get(nam);

		return (val == null ? 0 : val.doubleValue());
	}

	/** Gets an unmodifiable iterable of the variables in this evaluator. */
	public Iterable<Map.Entry<String, Double>> getVariables() {
		return Collections.unmodifiableMap(variables).entrySet();
	}

	/** Set a named variable (variables names are not case-sensitive). */
	public MathEval setVariable(String nam, double val) {
		return setVariable(nam, Double.valueOf(val));
	}

	/**
	 * Set a named variable (variables names are not case-sensitive). If the
	 * value is null, the variable is removed.
	 */
	public MathEval setVariable(String nam, Double val) {
		validateName(nam);
		if (val == null) {
			variables.remove(nam);
		} else {
			variables.put(nam, val);
		}
		return this;
	}

	/** Clear all variables (constants are not affected). */
	public MathEval clear() {
		variables.clear();
		return this;
	}

	/**
	 * Clear all variables prefixed by the supplied string followed by a dot,
	 * such that they match "Prefix.xxx".
	 */
	public MathEval clear(String pfx) {
		variables.subMap((pfx + "."), (pfx + "." + Character.MAX_VALUE))
				.clear();
		return this;
	}

	/**
	 * Get whether a variable which is used in an expression is required to be
	 * explicitly set. If not explicitly set, the value 0.0 is assumed.
	 */
	public boolean getVariableRequired() {
		return relaxed;
	}

	/**
	 * Set whether a variable which is used in an expression is required to be
	 * explicitly set. If not explicitly set, the value 0.0 is assumed.
	 */
	public MathEval setVariableRequired(boolean val) {
		relaxed = (!val);
		return this;
	}

	private void validateName(String nam) {
		if (!Character.isLetter(nam.charAt(0))) {
			throw new IllegalArgumentException(
					"Names for constants, variables and functions must start with a letter");
		}
		if (nam.indexOf('(') != -1 || nam.indexOf(')') != -1) {
			throw new IllegalArgumentException(
					"Names for constants, variables and functions may not contain a parenthesis");
		}
	}

	// *************************************************************************************************
	// INSTANCE METHODS - PUBLIC API
	// *************************************************************************************************

	/**
	 * Evaluate this expression.
	 */
	public double evaluate(String exp) throws NumberFormatException,
			ArithmeticException {
		expression = exp;
		isConstant = true;
		offset = 0;
		return _evaluate(0, (exp.length() - 1));
	}

	/**
	 * Return whether the previous expression evaluated was constant (i.e.
	 * contained no variables). This is useful when optimizing to store the
	 * result instead of repeatedly evaluating a constant expression like "2+2".
	 */
	public boolean previousExpressionConstant() {
		return isConstant;
	}

	/**
	 * Return a set of the variables in the supplied expression. Note:
	 * Substitutions which are in the constant table are not included.
	 */
	public Set<String> getVariablesWithin(String exp) {
		Set<String> all = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
		String add = null;

		if (separators == null) {
			StringBuilder sep = new StringBuilder(10);
			for (char chr = 0; chr < operators.length; chr++) {
				if (operators[chr] != null && !operators[chr].internal) {
					sep.append(chr);
				}
			}
			sep.append("()");
			separators = sep.toString();
		}

		for (StringTokenizer tkz = new StringTokenizer(exp, separators, true); tkz
				.hasMoreTokens();) {
			String tkn = tkz.nextToken().trim();

			if (tkn.length() != 0 && Character.isLetter(tkn.charAt(0))) {
				add = tkn;
			} else if (tkn.length() == 1 && tkn.charAt(0) == '(') {
				add = null;
			} else if (add != null && !constants.containsKey(add)) {
				all.add(add);
			}
		}
		if (add != null && !constants.containsKey(add)) {
			all.add(add);
		}
		return all;
	}

	// *************************************************************************************************
	// INSTANCE METHODS - PRIVATE IMPLEMENTATION
	// *************************************************************************************************

	/**
	 * Evaluate a complete (sub-)expression.
	 * 
	 * @param beg
	 *            Inclusive begin offset for subexpression.
	 * @param end
	 *            Inclusive end offset for subexpression.
	 */
	private double _evaluate(int beg, int end) throws NumberFormatException,
			ArithmeticException {
		return _evaluate(beg, end, 0.0, OPERAND, getOperator('='));
	}

	/**
	 * Evaluate the next operand of an expression.
	 * 
	 * @param beg
	 *            Inclusive begin offset for subexpression.
	 * @param end
	 *            Inclusive end offset for subexpression.
	 * @param pnd
	 *            Pending operator (operator previous to this subexpression).
	 * @param lft
	 *            Left-value with which to initialize this subexpression.
	 * @param cur
	 *            Current operator (the operator for this subexpression).
	 */
	private double _evaluate(int beg, int end, double lft, Operator pnd,
			Operator cur) throws NumberFormatException, ArithmeticException {
		Operator nxt = OPERAND; // next operator
		int ofs; // current expression offset

		for (ofs = beg; (ofs = skipWhitespace(expression, ofs, end)) <= end; ofs++) {
			boolean fnc = false;
			double rgt = Double.NaN; // next operand (right-value) to process

			for (beg = ofs; ofs <= end; ofs++) {
				char chr = expression.charAt(ofs);
				if ((nxt = getOperator(chr)) != OPERAND) {
					if (nxt.internal) {
						nxt = OPERAND;
					} // must kill operator to prevent spurious "Expression
						// ends with a blank sub-expression" at end of function
					else {
						break;
					}
				} else if (chr == ')' || chr == ',') { // end of subexpression
														// or function argument.
					break;
				}
			}

			EvaluateOperand: {
				char ch0 = expression.charAt(beg);
				boolean alp = Character.isLetter(ch0);

				if (cur.unary != LEFT_SIDE) {
					if (ch0 == '+') {
						continue;
					} // unary '+': no-op; i.e. +(-1) == -1
					if (ch0 == '-') {
						nxt = getOperator('±');
					} // unary '-': right-binding, high precedence operation
						// (different from subtract)
				}

				if (beg == ofs
						&& (cur.unary == LEFT_SIDE || nxt.unary == RIGHT_SIDE)) {
					rgt = Double.NaN; // left-binding unary operator; right
										// value will not be used and should be
										// blank
				} else if (ch0 == '(') {
					rgt = _evaluate(beg + 1, end);
					ofs = skipWhitespace(expression, offset + 1, end); // skip
																		// past
																		// ')'
																		// and
																		// any
																		// following
																		// whitespace
					nxt = (ofs <= end ? getOperator(expression.charAt(ofs))
							: OPERAND); // modify next operator
				} else if (alp && nxt.symbol == '(') {
					rgt = doFunction(beg, end);
					ofs = skipWhitespace(expression, offset + 1, end); // skip
																		// past
																		// ')'
																		// and
																		// any
																		// following
																		// whitespace
					nxt = (ofs <= end ? getOperator(expression.charAt(ofs))
							: OPERAND); // modify next operator
				} else if (alp) {
					rgt = doNamedVal(beg, (ofs - 1));
				} else {
					try {
						if (stringOfsEq(expression, beg, "0x")) {
							rgt = (double) Long.parseLong(expression.substring(
									beg + 2, ofs).trim(), 16);
						} else {
							rgt = Double.parseDouble(expression.substring(beg,
									ofs).trim());
						}
					} catch (NumberFormatException thr) {
						throw exception(beg, "Invalid numeric value \""
								+ expression.substring(beg, ofs).trim() + "\"");
					}
				}
			}

			if (opPrecedence(cur, LEFT_SIDE) < opPrecedence(nxt, RIGHT_SIDE)) { // correct
																				// even
																				// for
																				// last
																				// (non-operator)
																				// character,
																				// since
																				// non-operators
																				// have
																				// the
																				// artificial
																				// "precedence"
																				// zero
				rgt = _evaluate((ofs + 1), end, rgt, cur, nxt); // from after
																// operator to
																// end of
																// current
																// subexpression
				ofs = offset; // modify offset to after subexpression
				nxt = (ofs <= end ? getOperator(expression.charAt(ofs))
						: OPERAND); // modify next operator
			}

			lft = doOperation(beg, lft, cur, rgt);

			cur = nxt;
			if (opPrecedence(pnd, LEFT_SIDE) >= opPrecedence(cur, RIGHT_SIDE)) {
				break;
			}
			if (cur.symbol == '(') {
				ofs--;
			} // operator omitted for implicit multiplication of subexpression
		}
		if (ofs > end && cur != OPERAND) {
			if (cur.unary == LEFT_SIDE) {
				lft = doOperation(beg, lft, cur, Double.NaN);
			} else {
				throw exception(ofs,
						"Expression ends with a blank operand after operator '"
								+ nxt.symbol + "'");
			}
		}
		offset = ofs;
		return lft;
	}

	private Operator getOperator(char chr) {
		if (chr < operators.length) {
			Operator opr = operators[chr];
			if (opr != null) {
				return opr;
			}
		}
		return OPERAND;
	}

	private int opPrecedence(Operator opr, int sid) {
		if (opr == null) {
			return Integer.MIN_VALUE;
		} // not an operator
		else if (opr.unary == NO_SIDE || opr.unary != sid) {
			return (sid == LEFT_SIDE ? opr.precedenceL : opr.precedenceR);
		} // operator is binary or is unary and bound to the operand on the
			// other side
		else {
			return Integer.MAX_VALUE;
		} // operator is unary and associates with the operand on this side
	}

	private double doOperation(int beg, double lft, Operator opr, double rgt) {
		if (opr.unary != RIGHT_SIDE && Double.isNaN(lft)) {
			throw exception(beg, "Mathematical NaN detected in right-operand");
		}
		if (opr.unary != LEFT_SIDE && Double.isNaN(rgt)) {
			throw exception(beg, "Mathematical NaN detected in left-operand");
		}

		try {
			return opr.handler.evaluateOperator(lft, opr.symbol, rgt);
		} catch (ArithmeticException thr) {
			throw exception(beg, "Mathematical expression \"" + expression
					+ "\" failed to evaluate", thr);
		} catch (UnsupportedOperationException thr) {
			int tmp = beg;
			while (tmp > 0 && getOperator(expression.charAt(tmp)) == null) {
				tmp--;
			} // set up for offset of the offending operator
			throw exception(
					tmp,
					"Operator \""
							+ opr.symbol
							+ "\" not handled by math engine (Programmer error: The list of operators is inconsistent within the engine)");
		}
	}

	private double doFunction(int beg, int end) {
		int argbeg;

		for (argbeg = beg; argbeg <= end && expression.charAt(argbeg) != '('; argbeg++) {
			;
		}

		String fncnam = expression.substring(beg, argbeg).trim();
		ArgParser fncargs = new ArgParser(argbeg, end);
		FunctionHandler fnchdl = null;

		try {
			if ((fnchdl = pureFunctions.get(fncnam)) != null) {
				return fnchdl.evaluateFunction(fncnam, fncargs);
			} else if ((fnchdl = impureFunctions.get(fncnam)) != null) {
				isConstant = false; // impure functions cannot be guaranteed to
									// be constant
				return fnchdl.evaluateFunction(fncnam, fncargs);
			}
			fncargs = null; // suppress check for too many fncargs
		} catch (ArithmeticException thr) {
			fncargs = null;
			throw thr;
		} catch (NoSuchMethodError thr) {
			fncargs = null;
			throw exception(beg, "Function not supported in this JVM: \""
					+ fncnam + "\"");
		} catch (UnsupportedOperationException thr) {
			fncargs = null;
			throw exception(beg, thr.getMessage());
		} catch (Throwable thr) {
			fncargs = null;
			throw exception(beg,
					"Unexpected exception parsing function arguments", thr);
		} finally {
			if (fncargs != null) {
				if (fncargs.hasNext()) {
					throw exception(fncargs.getIndex(),
							"Function has too many arguments");
				}
				offset = fncargs.getIndex();
			}
		}
		throw exception(beg, "Function \"" + fncnam + "\" not recognized");
	}

	private double doNamedVal(int beg, int end) {
		while (beg < end && Character.isWhitespace(expression.charAt(end))) {
			end--;
		} // since a letter triggers a named value, this can never reduce to
			// beg==end

		String nam = expression.substring(beg, (end + 1));
		Double val;

		if ((val = constants.get(nam)) != null) {
			return val.doubleValue();
		} else if ((val = variables.get(nam)) != null) {
			isConstant = false;
			return val.doubleValue();
		} else if (relaxed) {
			isConstant = false;
			return 0.0;
		}

		throw exception(beg, "Unrecognized constant or variable \"" + nam
				+ "\"");
	}

	private ArithmeticException exception(int ofs, String txt) {
		return new ArithmeticException(txt + " at offset " + ofs
				+ " in expression \"" + expression + "\"");
	}

	private ArithmeticException exception(int ofs, String txt, Throwable thr) {
		return new ArithmeticException(
				txt
						+ " at offset "
						+ ofs
						+ " in expression \""
						+ expression
						+ "\""
						+ " (Cause: "
						+ (thr.getMessage() != null ? thr.getMessage() : thr
								.toString()) + ")");
	}

	private boolean stringOfsEq(String str, int ofs, String val) {
		return str.regionMatches(true, ofs, val, 0, val.length());
	}

	private int skipWhitespace(String exp, int ofs, int end) {
		while (ofs <= end && Character.isWhitespace(exp.charAt(ofs))) {
			ofs++;
		}
		return ofs;
	}

	// *************************************************************************************************
	// INSTANCE INNER CLASSES - FUNCTION ARGUMENT PARSER
	// *************************************************************************************************

	/**
	 * An abstract parser for function arguments.
	 */
	public final class ArgParser {
		final int exEnd;

		int index;

		ArgParser(int excstr, int excend) {
			exEnd = excend;

			index = (excstr + 1);

			index = skipWhitespace(expression, index, exEnd - 1);
		}

		/**
		 * Parse the next argument, throwing an exception if there are no more
		 * arguments.
		 * 
		 * @throws ArithmeticException
		 *             If there are no more arguments.
		 */
		public double next() {
			if (!hasNext()) {
				throw exception(index, "Function has too few arguments");
			}
			return _next();
		}

		/**
		 * Parse the next argument, returning the supplied default if there are
		 * no more arguments.
		 */
		public double next(double dft) {
			if (!hasNext()) {
				return dft;
			}
			return _next();
		}

		private double _next() {
			if (expression.charAt(index) == ',') {
				index++;
			}
			double ret = _evaluate(index, exEnd);
			index = offset;
			return ret;
		}

		/** Test whether there is another argument to parse. */
		public boolean hasNext() {
			return (expression.charAt(index) != ')');
		}

		int getIndex() {
			return index;
		}
	}

	// *************************************************************************************************
	// STATIC NESTED CLASSES - OPERATOR
	// *************************************************************************************************

	/**
	 * Operator Structure.
	 * <p>
	 * This class is immutable and threadsafe, but note that whether it can be
	 * used in multiple MathEval instances (as opposed to for multiple operators
	 * in one instance) depends on the threadsafety of the handler it contains.
	 */
	static public final class Operator extends Object {
		final char symbol; // parser symbol for this operator
		final int precedenceL; // precedence when on the left
		final int precedenceR; // precedence when on the right
		final int unary; // unary operator binding: left, right, or neither
		final boolean internal; // internal pseudo operator
		final OperatorHandler handler;

		/**
		 * Create a binary operator with the same precedence on the left and
		 * right.
		 */
		public Operator(char sym, int prc, OperatorHandler hnd) {
			this(sym, prc, prc, NO_SIDE, false, hnd);
		}

		/**
		 * Create an operator which may have different left and right precedence
		 * and/or may be unary.
		 * <p>
		 * Using different precedence for one side allows afinity binding such
		 * that consecutive operators are evaluated left to right.
		 * <p>
		 * Flagging an operator as unary binds the precedence for the specified
		 * side such that it always has maximum precedence when considered from
		 * the opposite side.
		 */
		public Operator(char sym, int prclft, int prcrgt, int unibnd,
				OperatorHandler hnd) {
			this(sym, prclft, prcrgt, unibnd, false, hnd);

			if (prclft < 0 || prclft > 99) {
				throw new IllegalArgumentException(
						"Operator precendence must be 0 - 99");
			}
			if (prcrgt < 0 || prcrgt > 99) {
				throw new IllegalArgumentException(
						"Operator precendence must be 0 - 99");
			}
			if (handler == null) {
				throw new IllegalArgumentException(
						"Operator handler is required");
			}
		}

		Operator(char sym, int prclft, int prcrgt, int unibnd, boolean intern,
				OperatorHandler hnd) {
			symbol = sym;
			precedenceL = prclft;
			precedenceR = prcrgt;
			unary = unibnd;
			internal = intern;
			handler = hnd;
		}

		public String toString() {
			return ("MathOperator['" + symbol + "']");
		}
	}

	// *************************************************************************************************
	// STATIC NESTED CLASSES - OPERATION EVALUATOR INTERFACE
	// *************************************************************************************************

	static public interface OperatorHandler {
		public double evaluateOperator(double lft, char opr, double rgt)
				throws ArithmeticException;
	}

	// *************************************************************************************************
	// STATIC NESTED CLASSES - FUNCTION EVALUATOR INTERFACE
	// *************************************************************************************************

	static public interface FunctionHandler {
		public double evaluateFunction(String fncnam, ArgParser fncargs)
				throws ArithmeticException;
	}

	// *************************************************************************************************
	// STATIC NESTED CLASSES - DEFAULT OPERATOR/FUNCTION IMPLEMENTATION
	// *************************************************************************************************

	/**
	 * An implementation of the default supported operations and functions.
	 */
	static class DefaultImpl extends Object implements OperatorHandler,
			FunctionHandler {
		private DefaultImpl() {
		}

		// To add/remove operators change evaluateOperator() and registration
		public double evaluateOperator(double lft, char opr, double rgt) {
			switch (opr) {
			case '=':
				return rgt; // final assignment
			case '^':
				return Math.pow(lft, rgt); // power
			case '±':
				return -rgt; // unary negation
			case '*':
				return lft * rgt; // multiply (classical)
			case '×':
				return lft * rgt; // multiply (because it's a Unicode world
									// out there)
			case '·':
				return lft * rgt; // multiply (because it's a Unicode world
									// out there)
			case '(':
				return lft * rgt; // multiply implicitly due to brackets (e.g
									// (a)(b)
			case '/':
				return lft / rgt; // divide (classical computing)
			case '÷':
				return lft / rgt; // divide (because it's a Unicode world out
									// there)
			case '%':
				return lft % rgt; // remainder
			case '+':
				return lft + rgt; // add/unary-positive
			case '-':
				return lft - rgt; // subtract/unary-negative
			default:
				throw new UnsupportedOperationException(
						"MathEval internal operator setup is incorrect - internal operator \""
								+ opr + "\" not handled");
			}
		}

		// To add/remove functions change evaluateOperator() and registration
		public double evaluateFunction(String fncnam, ArgParser fncargs)
				throws ArithmeticException {
			switch (Character.toLowerCase(fncnam.charAt(0))) {
			case 'a': {
				if (fncnam.equalsIgnoreCase("abs")) {
					return Math.abs(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("acos")) {
					return Math.acos(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("asin")) {
					return Math.asin(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("atan")) {
					return Math.atan(fncargs.next());
				}
			}
				break;
			case 'c': {
				if (fncnam.equalsIgnoreCase("cbrt")) {
					return Math.cbrt(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("ceil")) {
					return Math.ceil(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("cos")) {
					return Math.cos(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("cosh")) {
					return Math.cosh(fncargs.next());
				}
			}
				break;
			case 'e': {
				if (fncnam.equalsIgnoreCase("exp")) {
					return Math.exp(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("expm1")) {
					return Math.expm1(fncargs.next());
				}
			}
				break;
			case 'f': {
				if (fncnam.equalsIgnoreCase("floor")) {
					return Math.floor(fncargs.next());
				}
			}
				break;
			case 'g': {
				// if(fncnam.equalsIgnoreCase("getExponent" )) { return
				// Math.getExponent(fncargs.next()); } needs Java 6
			}
				break;
			case 'l': {
				if (fncnam.equalsIgnoreCase("log")) {
					return Math.log(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("log10")) {
					return Math.log10(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("log1p")) {
					return Math.log1p(fncargs.next());
				}
			}
				break;
			case 'm': {
				if (fncnam.equalsIgnoreCase("max")) {
					return Math.max(fncargs.next(), fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("min")) {
					return Math.min(fncargs.next(), fncargs.next());
				}
			}
				break;
			case 'n': {
				// if(fncnam.equalsIgnoreCase("nextUp" )) { return Math.nextUp
				// (fncargs.next()); } needs Java 6
			}
				break;
			case 'r': {
				if (fncnam.equalsIgnoreCase("random")) {
					return Math.random();
				} // impure
				if (fncnam.equalsIgnoreCase("round")) {
					return Math.round(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("roundHE")) {
					return Math.rint(fncargs.next());
				} // round half-even
			}
				break;
			case 's': {
				if (fncnam.equalsIgnoreCase("signum")) {
					return Math.signum(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("sin")) {
					return Math.sin(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("sinh")) {
					return Math.sinh(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("sqrt")) {
					return Math.sqrt(fncargs.next());
				}
			}
				break;
			case 't': {
				if (fncnam.equalsIgnoreCase("tan")) {
					return Math.tan(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("tanh")) {
					return Math.tanh(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("toDegrees")) {
					return Math.toDegrees(fncargs.next());
				}
				if (fncnam.equalsIgnoreCase("toRadians")) {
					return Math.toRadians(fncargs.next());
				}
			}
				break;
			case 'u': {
				if (fncnam.equalsIgnoreCase("ulp")) {
					return Math.ulp(fncargs.next());
				}
			}
				break;
			// no default
			}
			throw new UnsupportedOperationException(
					"MathEval internal function setup is incorrect - internal function \""
							+ fncnam + "\" not handled");
		}

		static final DefaultImpl INSTANCE = new DefaultImpl();

		static private final Operator OPR_EQU = new Operator('=', 99, 99,
				RIGHT_SIDE, true, DefaultImpl.INSTANCE); // simple
															// assignment, used
															// as the final
															// operation, must
															// be maximum
															// precedence
		static private final Operator OPR_PWR = new Operator('^', 80, 81,
				NO_SIDE, false, DefaultImpl.INSTANCE); // power
		static private final Operator OPR_NEG = new Operator('±', 60, 60,
				RIGHT_SIDE, true, DefaultImpl.INSTANCE); // unary negation
		static private final Operator OPR_MLT1 = new Operator('*', 40,
				DefaultImpl.INSTANCE); // multiply (classical)
		static private final Operator OPR_MLT2 = new Operator('×', 40,
				DefaultImpl.INSTANCE); // multiply (because it's a Unicode
										// world out there)
		static private final Operator OPR_MLT3 = new Operator('·', 40,
				DefaultImpl.INSTANCE); // multiply (because it's a Unicode
										// world out there)
		static private final Operator OPR_BKT = new Operator('(', 40,
				DefaultImpl.INSTANCE); // multiply (implicit due to brackets,
										// e.g "(a)(b)")
		static private final Operator OPR_DIV1 = new Operator('/', 40,
				DefaultImpl.INSTANCE); // divide (classical computing)
		static private final Operator OPR_DIV2 = new Operator('÷', 40,
				DefaultImpl.INSTANCE); // divide (because it's a Unicode world
										// out there)
		static private final Operator OPR_MOD = new Operator('%', 40,
				DefaultImpl.INSTANCE); // remainder
		static private final Operator OPR_ADD = new Operator('+', 20,
				DefaultImpl.INSTANCE); // add/unary-positive
		static private final Operator OPR_SUB = new Operator('-', 20,
				DefaultImpl.INSTANCE); // subtract/unary-negative

		// To add/remove operators change evaluateOperator() and registration
		static void registerOperators(MathEval tgt) {
			tgt.setOperator(OPR_EQU);
			tgt.setOperator(OPR_PWR);
			tgt.setOperator(OPR_NEG);
			tgt.setOperator(OPR_MLT1);
			tgt.setOperator(OPR_MLT2);
			tgt.setOperator(OPR_MLT3);
			tgt.setOperator(OPR_BKT);
			tgt.setOperator(OPR_DIV1);
			tgt.setOperator(OPR_DIV2);
			tgt.setOperator(OPR_MOD);
			tgt.setOperator(OPR_ADD);
			tgt.setOperator(OPR_SUB);
		}

		// To add/remove functions change evaluateOperator() and registration
		static void registerFunctions(MathEval tgt) {
			tgt.setFunctionHandler("abs", INSTANCE);
			tgt.setFunctionHandler("acos", INSTANCE);
			tgt.setFunctionHandler("asin", INSTANCE);
			tgt.setFunctionHandler("atan", INSTANCE);
			tgt.setFunctionHandler("cbrt", INSTANCE);
			tgt.setFunctionHandler("ceil", INSTANCE);
			tgt.setFunctionHandler("cos", INSTANCE);
			tgt.setFunctionHandler("cosh", INSTANCE);
			tgt.setFunctionHandler("exp", INSTANCE);
			tgt.setFunctionHandler("expm1", INSTANCE);
			tgt.setFunctionHandler("floor", INSTANCE);
			// t.setFunctionHandler("getExponent" ,INSTANCE); // needs Java 6
			tgt.setFunctionHandler("log", INSTANCE);
			tgt.setFunctionHandler("log10", INSTANCE);
			tgt.setFunctionHandler("log1p", INSTANCE);
			tgt.setFunctionHandler("max", INSTANCE);
			tgt.setFunctionHandler("min", INSTANCE);
			// t.setFunctionHandler("nextUp" ,INSTANCE); // needs Java 6
			tgt.setFunctionHandler("random", INSTANCE, true); // impure
			tgt.setFunctionHandler("round", INSTANCE);
			tgt.setFunctionHandler("roundHE", INSTANCE); // round half-even
			tgt.setFunctionHandler("signum", INSTANCE);
			tgt.setFunctionHandler("sin", INSTANCE);
			tgt.setFunctionHandler("sinh", INSTANCE);
			tgt.setFunctionHandler("sqrt", INSTANCE);
			tgt.setFunctionHandler("tan", INSTANCE);
			tgt.setFunctionHandler("tanh", INSTANCE);
			tgt.setFunctionHandler("toDegrees", INSTANCE);
			tgt.setFunctionHandler("toRadians", INSTANCE);
			tgt.setFunctionHandler("ulp", INSTANCE);
		}
	}

	// *************************************************************************************************
	// STATIC PROPERTIES
	// *************************************************************************************************

	/** Operator/operand on on the left. */
	static public final int LEFT_SIDE = 'L';
	/** Operator/operand on on the right. */
	static public final int RIGHT_SIDE = 'R';
	/** Operator/operand side is immaterial. */
	static public final int NO_SIDE = 'B';

	/** Implementation for the default operators. */
	static public final OperatorHandler DFT_OPERATOR_HANDLER = DefaultImpl.INSTANCE;

	/** Implementation for the default function (java.lang.Math). */
	static public final FunctionHandler DFT_FUNCTION_HANDLER = DefaultImpl.INSTANCE;

	static private final Operator OPERAND = new Operator('\0', 0, 0, NO_SIDE,
			false, null); // special "non-operator" representing an operand
							// character

	// *************************************************************************************************
	// STATIC METHODS - UTILITY
	// *************************************************************************************************

} // END PUBLIC CLASS
